# -*- coding: utf-8 -*-

import os
import sys

if __name__ == "__main__":
    currentdir = os.path.dirname( os.path.abspath( sys.argv[0] ) )
    basedir = os.path.abspath( os.path.join( currentdir, ".." ) )
    pythondir = basedir
    sys.path.insert( 0, pythondir )
    pass

from logger import get_file_logger

from picklerpc.msghandler import RequestHandler
from picklerpc.sendmsg import sendmsg
from picklerpc import Server
from time import time, sleep
from threading import RLock
from argparse import ArgumentParser
import random
from datetime import timedelta
from service import default_request_timeout, default_niceness_level, Py34ServiceManager as ServiceManager

__all__ = ['TaskHandler', 'TaskServer', 'TaskRequest', 'TaskManager']

#---------------------------------------------

class TaskHandler(RequestHandler):

    def _statistics_handle(self, data):
        """
        @param data: simple object data
        @type: dict

        @return: python simple object data, picklable
        @rtype: mixed
        """
        result = False
        
        request = self.server.getStatisticsRequest()

        if data["task"] == "ping":
            return request.ping()
        if data["task"] == "quit":
            return request.quit()
        if data["task"] == "reset":
            return request.reset()
        if data["task"] == "get_address":
            return request.get_address()
        if data["task"] == "inc":
            data = data["data"]
            return request.inc(data["param"], data["value"])
        if data["task"] == "dec":
            data = data["data"]
            return request.dec(data["param"], data["value"])
        if data["task"] == "set":
            data = data["data"]
            return request.set(data["param"], data["value"])
        if data["task"] == "is_updated":
            return request.is_updated()
        if data["task"] == "get_update":
            return request.get_update()
        if data["task"] == "set_hashing":
            data = data["data"]
            return request.set_hashing(
                data["device"],
                data["name"],
                data["size"],
                data["readed"],
                data["start_time"]
            )
        if data["task"] == "inc_hashing":
            data = data["data"]
            return request.inc_hashing(
                data["device"],
                data["readed"]
            )
        if data["task"] == "set_imaging":
            data = data["data"]
            return request.set_imaging(
                data["name"],
                data["count"],
                data["processed"],
                data["start_time"]
            )
        if data["task"] == "inc_imaging":
            data = data["data"]
            return request.inc_imaging(
                data["processed"]
            )
        return result


    def _hash_handle(self, data):
        """
        @param data: simple object data
        @type: dict

        @return: python simple object data, picklable
        @rtype: mixed
        """
        result = False

        if data["task"] == "ping":
            data = data["data"]
            request = self.server.getHashRequest(data["device"])
            return request.ping(data["device"])
        if data["task"] == "get_address":
            data = data["data"]
            request = self.server.getHashRequest(data["device"])
            return request.get_address(data["device"])
        if data["task"] == "quit":
            request = self.server.getHashRequest(None)
            return request.quit()
        if data["task"] == "hash":
            data = data["data"]
            request = self.server.getHashRequest(data["device"])
            return request.hash(
                data["file_id"],
                data["device"],
                data["file_size"],
                data["full_path"]
            )
        return result


    def _check_file_handle(self, data):
        """
        @param data: simple object data
        @type: dict

        @return: python simple object data, picklable
        @rtype: mixed
        """
        result = False

        if data["task"] == "ping":
            data = data["data"]
            request = self.server.getCheckFileRequest(data["device"])
            return request.ping(data["device"])
        if data["task"] == "get_address":
            data = data["data"]
            request = self.server.getCheckFileRequest(data["device"])
            return request.get_address(data["device"])
        if data["task"] == "quit":
            request = self.server.getCheckFileRequest(None)
            return request.quit()
        if data["task"] == "check":
            data = data["data"]
            request = self.server.getCheckFileRequest(data["device"])
            return request.check(
                data["device"],
                data["dir_id"],
                data["parent_path"],
                data["name"]
            )
        return result


    def _scan_directory_handle(self, data):
        """
        @param data: simple object data
        @type: dict

        @return: python simple object data, picklable
        @rtype: mixed
        """
        result = False

        if data["task"] == "ping":
            data = data["data"]
            request = self.server.getScanDirectoryRequest(data["device"])
            return request.ping(data["device"])
        if data["task"] == "get_address":
            data = data["data"]
            request = self.server.getScanDirectoryRequest(data["device"])
            return request.get_address(data["device"])
        if data["task"] == "quit":
            request = self.server.getScanDirectoryRequest(None)
            return request.quit()
        if data["task"] == "scan":
            data = data["data"]
            request = self.server.getScanDirectoryRequest(data["device"])
            return request.scan(
                data["device"],
                data["dir_id"],
                data["parent_path"],
                data["name"]
            )
        return result


    def _process_archive_handle(self, data):
        """
        @param data: simple object data
        @type: dict

        @return: python simple object data, picklable
        @rtype: mixed
        """
        result = False

        request = self.server.getProcessArchiveRequest()

        if data["task"] == "ping":
            return request.ping()
        if data["task"] == "quit":
            return request.quit()
        if data["task"] == "get_address":
            return request.get_address()
        if data["task"] == "process":
            data = data["data"]
            return request.process(
                data["file_id"],
                data["file_path"],
                data["mime"],
                data["fsize"]
            )
        return result


    def _request_handle(self, data):
        """
        @param data: simple object data
        @type: dict

        @return: python simple object data, picklable
        @rtype: mixed
        """
        result = False

        if not (type(data) is dict):
            return result

        if data["service"] is None:
            if data["task"] == "ping":
                return "pong"
            if data["task"] == "stop":
                return self.server.stop()
            if data["task"] == "quit":
                return self.server.quit()
        if data["service"] == "statistics":
            result = self._statistics_handle(data)
        if data["service"] == "hash":
            result = self._hash_handle(data)
        if data["service"] == "check_file":
            result = self._check_file_handle(data)
        if data["service"] == "scan_directory":
            result = self._scan_directory_handle(data)
        if data["service"] == "process_archive":
            result = self._process_archive_handle(data)

        return result

    pass

#---------------------------------------------

class TaskServer(Server):
    
    _statistics_manager = None
    _hash_manager = None
    _check_file_manager = None
    _scan_directory_manager = None
    _process_archive_manager = None

    _server_statistics_lock = None
    _server_hash_lock = None
    _server_check_file_lock = None
    _server_scan_directory_lock = None
    _server_process_archive_lock = None

    log = None

    def server_activate(self):
        self._server_statistics_lock = RLock()
        self._server_hash_lock = RLock()
        self._server_check_file_lock = RLock()
        self._server_scan_directory_lock = RLock()
        self._server_process_archive_lock = RLock()

        self.log = get_file_logger("TaskServer")

        return Server.server_activate(self)


    def getStatisticsManager(self):

        self._server_statistics_lock.acquire()

        if not self._statistics_manager:

            from service.statistics import StatisticsManager

            self._statistics_manager = StatisticsManager()
            self._statistics_manager.start_service()

        self._server_statistics_lock.release()

        return self._statistics_manager

    def getStatisticsRequest(self):
        return self.getStatisticsManager().get_request()


    def getProcessArchiveManager(self):

        self._server_process_archive_lock.acquire()

        if not self._process_archive_manager:

            from service.process_archive import ProcessArchiveManager

            self._process_archive_manager = ProcessArchiveManager(self.server_address)
            self._process_archive_manager.start_service()

        self._server_process_archive_lock.release()

        return self._process_archive_manager

    def getProcessArchiveRequest(self):
        return self.getProcessArchiveManager().get_request()


    def getHashManager(self, device):

        self._server_hash_lock.acquire()

        if not self._hash_manager:

            from service.hash import HashManager

            self._hash_manager = HashManager(self.server_address)

        if device:
            self._hash_manager.start_service(device)

        self._server_hash_lock.release()

        return self._hash_manager

    def getHashRequest(self, device):
        return self.getHashManager(device).get_request()


    def getCheckFileManager(self, device):

        self._server_check_file_lock.acquire()

        if not self._check_file_manager:

            from service.check_file import CheckFileManager

            self._check_file_manager = CheckFileManager(self.server_address)

        if device:
            self._check_file_manager.start_service(device)

        self._server_check_file_lock.release()

        return self._check_file_manager

    def getCheckFileRequest(self, device):
        return self.getCheckFileManager(device).get_request()


    def getScanDirectoryManager(self, device):

        self._server_scan_directory_lock.acquire()

        if not self._scan_directory_manager:

            from service.scan_directory import ScanDirectoryManager

            self._scan_directory_manager = ScanDirectoryManager(self.server_address)

        if device:
            self._scan_directory_manager.start_service(device)

        self._server_scan_directory_lock.release()

        return self._scan_directory_manager

    def getScanDirectoryRequest(self, device):
        return self.getScanDirectoryManager(device).get_request()


    def stop(self, all=False):

        if self._scan_directory_manager:
            self.getScanDirectoryRequest(None).quit()
            while self.getScanDirectoryRequest(None).ping_all():
                sleep(0.1)
            self._scan_directory_manager = None

        if self._check_file_manager:
            self.getCheckFileRequest(None).quit()
            while self.getCheckFileRequest(None).ping_all():
                sleep(0.1)
            self._check_file_manager = None

        if self._hash_manager:
            self.getHashRequest(None).quit()
            while self.getHashRequest(None).ping_all():
                sleep(0.1)
            self._hash_manager = None

        if self._process_archive_manager:
            self.getProcessArchiveRequest().quit()
            while self.getProcessArchiveRequest().ping() == "pong":
                sleep(0.1)
            self._process_archive_manager = None

        if all:
            if self._statistics_manager:
                self.getStatisticsRequest().quit()
                while self.getStatisticsRequest().ping() == "pong":
                    sleep(0.1)
                self._statistics_manager = None

        return True


    def quit(self):
        self.log.info("quit event")

        self.stop(True)

        import signal

        self.log.debug("send SIGINT to self")
        os.kill( os.getpid(), signal.SIGINT )

        return True


    def terminate_self(self, signum, frame):
        self.log.debug("got SHUTDOWN signal")
        t_begin = time()
        self.stop(True)
        self.server_close()
        t_end = time()
        self.log.info("SHUTDOWN in %s sec" % (timedelta( seconds = t_end - t_begin ),))
        return

    pass

#---------------------------------------------


class StatisticsTaskRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def ping(self):
        return self.manager.sendmsg("statistics", "ping", {}, 0.1)

    def reset(self):
        return self.manager.sendmsg("statistics", "reset", {})

    def inc(self, param, value):
        return self.manager.sendmsg("statistics", "inc", {
            "param" : param,
            "value" : value
        })

    def dec(self, param, value):
        return self.manager.sendmsg("statistics", "dec", {
            "param" : param,
            "value" : value
        })

    def set(self, param, value):
        return self.manager.sendmsg("statistics", "set", {
            "param" : param,
            "value" : value
        })

    def is_updated(self):
        return self.manager.sendmsg("statistics", "is_updated", {})

    def get_update(self):
        return self.manager.sendmsg("statistics", "get_update", {})

    def set_hashing(self, device, name, size, readed, start_time):
        return self.manager.sendmsg("statistics", "set_hashing", {
            "device" : device,
            "name" : name,
            "size" : size,
            "readed" : readed,
            "start_time" : start_time
        })

    def inc_hashing(self, device, readed):
        return self.manager.sendmsg("statistics", "inc_hashing", {
            "device" : device,
            "readed" : readed
        })

    def set_imaging(self, name, count, processed, start_time):
        return self.manager.sendmsg("statistics", "set_imaging", {
            "name" : name,
            "count" : count,
            "processed" : processed,
            "start_time" : start_time
        })

    def inc_imaging(self, processed):
        return self.manager.sendmsg("statistics", "inc_imaging", {
            "processed" : processed
        })

    def get_address(self):
        return self.manager.sendmsg( "statistics", "get_address", {})

    def quit(self):
        return self.manager.sendmsg("statistics", "quit", {})

    pass


class HashTaskRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def ping(self, device):
        return self.manager.sendmsg( "hash", "ping", {"device":device}, 0.1)

    def get_address(self, device):
        return self.manager.sendmsg( "hash", "get_address", {"device":device})

    def hash(self, file_id, device, file_size, full_path):
        return self.manager.sendmsg( "hash", "hash", {
            "file_id" : file_id,
            "device" : device,
            "file_size" : file_size,
            "full_path" : full_path
        })

    def quit(self):
        return self.manager.sendmsg( "hash", "quit", {})

    pass


class CheckFileTaskRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def ping(self, device):
        return self.manager.sendmsg( "check_file", "ping", {"device":device}, 0.1)

    def get_address(self, device):
        return self.manager.sendmsg( "check_file", "get_address", {"device":device})

    def check(self, device, dir_id, parent_path, name):
        return self.manager.sendmsg( "check_file", "check", {
            "device" : device,
            "dir_id" : dir_id,
            "parent_path" : parent_path,
            "name" : name
        })

    def quit(self):
        return self.manager.sendmsg( "check_file", "quit", {})

    pass


class ScanDirectoryTaskRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def ping(self, device):
        return self.manager.sendmsg( "scan_directory", "ping", {"device":device}, 0.1)

    def get_address(self, device):
        return self.manager.sendmsg( "scan_directory", "get_address", {"device":device})

    def scan(self, device, dir_id, parent_path, name):
        return self.manager.sendmsg( "scan_directory", "scan", {
            "device" : device,
            "dir_id" : dir_id,
            "parent_path" : parent_path,
            "name" : name
        })

    def quit(self):
        return self.manager.sendmsg( "scan_directory", "quit", {})

    pass


class ProcessArchiveTaskRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def ping(self):
        return self.manager.sendmsg( "process_archive", "ping", {}, 0.1)

    def get_address(self):
        return self.manager.sendmsg( "process_archive", "get_address", {})

    def process(self, file_id, file_path, mime, fsize):
        return self.manager.sendmsg( "process_archive", "process", {
            "file_id" : file_id,
            "file_path" : file_path,
            "mime" : mime,
            "fsize" : fsize
        })

    def quit(self):
        return self.manager.sendmsg( "process_archive", "quit", {})

    pass



class TaskRequest(object):

    _options = None
    _statistics = None
    _hash = None
    _check_file = None
    _scan_directory = None
    _process_archive = None
    _image_vector = None

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def getStatistics(self):
        if not self._statistics:
            self._statistics = StatisticsTaskRequest(self.manager)
        return self._statistics

    def getCheckFile(self):
        if not self._check_file:
            self._check_file = CheckFileTaskRequest(self.manager)
        return self._check_file

    def getScanDirectory(self):
        if not self._scan_directory:
            self._scan_directory = ScanDirectoryTaskRequest(self.manager)
        return self._scan_directory

    def getHash(self):
        if not self._hash:
            self._hash = HashTaskRequest(self.manager)
        return self._hash

    def getProcessArchive(self):
        if not self._process_archive:
            self._process_archive = ProcessArchiveTaskRequest(self.manager)
        return self._process_archive

    def ping(self):
        return self.manager.sendmsg(None, "ping", {}, 0.1)

    def stop(self):
        return self.manager.sendmsg(None, "stop", {})

    def quit(self):
        return self.manager.sendmsg(None, "quit", {})

    pass


#---------------------------------------------

class TaskManager(ServiceManager):

    host = "127.0.1.1"
    port_min = 10000
    port_max = 20000
    port = None
    request = None

    address = None

    log = None

    def __init__(self, address = None):
        self.address = address

        self.log = get_file_logger("TaskManager")

        pass

    def start_service(self):

        from archive import which
        from subprocess import Popen

        self.port = random.randint(self.port_min, self.port_max)

        self.address = (self.host, self.port,)

        Popen([self.getPython(), "service/task.py",
               "--host", "%s" % self.host,
               "--port", "%s" % self.port
        ])

        # WAIT WHILE SERVICE STARTS

        pings = 100
        runing = False
        t_begin = time()
        while not runing:

            pong = self.get_request().ping()

            if pong == "pong":
                runing = True

            pings -= 1
            if pings <= 0:
                break

        if pings <= 0:
            self.port = None
            self.log.error("FAIL TO START service on %r in %s sec" % (
                self.address, timedelta( seconds = time() - t_begin ),
            ))
        else:
            self.log.info("STARTED service on %r in %s sec" % (
                self.address, timedelta( seconds = time() - t_begin ),
            ))

        return runing

    def sendmsg(self, service, task, data, timeout=default_request_timeout):
        try:
            result = sendmsg( self.address, {"service":service, "task":task, "data":data}, timeout=timeout )
            if result is None:
                raise ValueError("Result is NONE!")
        except Exception as e:
            result = None
            self.log.warning("SOCKET: %s" % ( e,))
            self.log.debug("FAIL SEND MESSAGE: service=%s, task=%s, data=%r" % (
                service, task, data,
            ))
        return result

    def quit(self):
        return self.get_request().quit()

    def get_request(self):
        if not self.request:
            self.request = TaskRequest(self)
        return self.request

    pass


#---------------------------------------------

def run_service(host, port):

    if hasattr(os, "nice"):
        os.nice(default_niceness_level)

    e_code = 0

    server = None
    try:
        SERVER_ADDRESS = (host, int(port),)

        server = TaskServer(SERVER_ADDRESS, TaskHandler, bind_and_activate=False)

        import signal

        signal.signal(signal.SIGINT, server.terminate_self)
        signal.signal(signal.SIGTERM, server.terminate_self)

        server.server_bind()
        server.server_activate()
        server.serve_forever()
    except Exception as e:
        e_code = 1
        if server and server.log:
            server.log.error("run_service: ERROR=%r" % (e,))
        pass

    return e_code


def launch():

    parser = ArgumentParser(description='Run task service.')
    parser.add_argument('--host', dest='host', action='store', type=str,
        help='Listen on TCP address')
    parser.add_argument('--port', dest='port', action='store', type=int,
        help='Listen on TCP port')

    args = parser.parse_args()
    return run_service(args.host, args.port)

if __name__ == "__main__":
    sys.exit(launch())
